#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <zlib.h>
#include <vector>
#include <algorithm>
#include "router.h"
#include "main.h"
#include "nic.h"
#include "link.h"
#include "app.h"
#include "router_common.h"
#include "sim_result.h"
#include "string.h"
#include "defines.h"
#include "shared.h"
#include "batch.h"
#include "rank.h"

#ifdef TR_INTEG
#undef INVALID
#include "processor.h"
#include "globals.h"

#undef INVALID
#define INVALID 1

#endif

int MAX_BATCH_ID=16;
//This could be cycles or packet based
int GlobalBatchMarkingCap;
int GlobalBatchCounter=0;
int GlobalPacketCounter=0;
int BatchInfoGatherInterval=10;
int GlobalBatchMode=BATCHING_MODE_NONE;
int PacketsInjected[MAX_NODES];
int BatchBarrierPenalty=4;
int GlobalOldestBatchId=0;
int GlobalCurrentBatchId=0;
double BatchRollOverDistance=0;
double BatchRollOverCounter=0;

void UpdateBatchId()
{
  if(GlobalBatchCounter >= GlobalBatchMarkingCap)
  {
    GlobalCurrentBatchId = (GlobalCurrentBatchId + 1)%MAX_BATCH_ID;
    //if(verbose == YES)
      printf("Updating Current Batch to %d injected %d packets in Last Batch @%lld!!\n", GlobalCurrentBatchId, GlobalPacketCounter,
          global_clock);
    for(int node=0; node<NUM_NODES; node++)
      last_batch_id[node] = GlobalCurrentBatchId; 

    GlobalBatchCounter = 0;
    GlobalPacketCounter = 0;
    for(int node=0; node<NUM_NODES; node++)
      PacketsInjected[node]=0;
  }
  return;
}

void UpdateBatchCounter()
{

  if(global_clock && global_clock % BatchInfoGatherInterval)
    return;

  if(GlobalBatchMode == BATCHING_GLOBAL_TIMEBASED)
  {
    GlobalBatchCounter += BatchInfoGatherInterval;
    for(int node=0; node<NUM_NODES; node++)
    {
      GlobalPacketCounter += PacketsInjected[node];
      PacketsInjected[node] = 0;
    }
    return;
  }
  if(GlobalBatchMode == BATCHING_GLOBAL_PACKETBASED)
  {
    for(int node=0; node<NUM_NODES; node++)
    {
      GlobalBatchCounter+= PacketsInjected[node];
      GlobalPacketCounter += PacketsInjected[node];
      PacketsInjected[node] = 0;
    }
    return;
  }
  
  if(GlobalBatchMode == BATCHING_GLOBAL_FULLBATCHING)
  {
    for(int node=0; node<NUM_NODES; node++)
    {
      GlobalBatchCounter+= PacketsInjected[node];
      GlobalPacketCounter += PacketsInjected[node];
      PacketsInjected[node] = 0;
    }
    return;
  }
  
  if(GlobalBatchMode == BATCHING_GLOBAL_TB_FULLBATCHING)
  {
    GlobalBatchCounter += BatchInfoGatherInterval;
    for(int node=0; node<NUM_NODES; node++)
    {
      GlobalPacketCounter += PacketsInjected[node];
      PacketsInjected[node] = 0;
    }
    return;
  }

}

bool mbox_batch_present(mbox_t *mbox,int BatchId)
{
  mbox_item_t *tmp;
  flit_t *flit_ptr;
  tmp = mbox->next;
  while(tmp != NULL)
  {
    flit_ptr = tmp->ptr;
    if(flit_ptr->batch_id == BatchId)
      return true;
    tmp = tmp->next;
  }
  return false;
}

bool OldestBatchActive()
{
  int node, pc, vc;

  for(node=0; node<NUM_NODES; node++)
  {
    NUM_PC     = router_info[node].num_pc;
    NUM_NIC_PC = router_info[node].num_nic_pc;
    NUM_VC     = router_info[node].num_vc;
    
    for(pc=0; pc<NUM_PC; pc++)
      for(vc=0; vc<NUM_VC; vc++)
      {
        if(mbox_batch_present(&(router_input_buf[node][pc][vc]),GlobalOldestBatchId))
          return true;
      
        if(pc < NUM_NIC_PC)
          if(mbox_batch_present(&(nic_output_buf[node][pc][vc]),GlobalOldestBatchId))
              return true;
      }//vc & pc
  }//node

  return false;
}

void UpdateOldestBatch()
{
  static long long BatchBarrierDelay = -1;

  if(OldestBatchActive() == false && 
     BatchBarrierDelay == -1 && 
     GlobalOldestBatchId != GlobalCurrentBatchId)
  {
    BatchBarrierDelay = global_clock + BatchBarrierPenalty;
  }

  if(BatchBarrierDelay != -1 && BatchBarrierDelay == global_clock)
  {
    //if(verbose == YES)
      printf("Updating Oldest Batch to %d @%lld!!\n", (GlobalOldestBatchId+1)%MAX_BATCH_ID,global_clock);
    GlobalOldestBatchId = (GlobalOldestBatchId + 1) % MAX_BATCH_ID;
    BatchBarrierDelay = -1;
    BatchRollOverDistance += GlobalCurrentBatchId > GlobalOldestBatchId ? 
      GlobalCurrentBatchId - GlobalOldestBatchId:
      GlobalCurrentBatchId + (MAX_BATCH_ID - GlobalOldestBatchId);
    BatchRollOverCounter++;
  }

}

int FindPriority(int RankNode, int InjectingNode)
{
  int priority = 0;
  int batch_weight = 0;
  int rank_weight = 0;

	if(GlobalBatchMode <= BATCHING_MODE_GLOBAL)
	{
		return 0;
	}
  batch_weight = MAX_BATCH_ID - last_batch_id[InjectingNode] - 1; 
  rank_weight = global_priority_matrix[InjectingNode][RankNode];


  if(GlobalBatchMode == BATCHING_GLOBAL_FULLBATCHING || 
     GlobalBatchMode == BATCHING_GLOBAL_TB_FULLBATCHING)
  {
    for(int i=0,b=GlobalOldestBatchId; i<MAX_BATCH_ID;i++)
    {
      if(last_batch_id[InjectingNode] == b)
        batch_weight = MAX_BATCH_ID - 1 - i;
      b = (b + 1)%MAX_BATCH_ID;
    }
  }
  if(GlobalBatchMode == BATCHING_RANKING_ONLY)
    batch_weight = 0;
  
  if(GlobalRankMode == RANKING_GLOBAL_BASE)
    rank_weight = 0;

  priority = batch_weight*num_priority_levels + rank_weight; 
 
  PacketsInjected[InjectingNode]++;
	
  return priority;
}


